<?php


/**
 * elasticsearch
 */

namespace App\Common\Utill;

use Elasticsearch\ClientBuilder;
use Exception;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Log;

class ElasticSearch
{
    private $client;
    private $type = "doc";
    private $host_name;
    private $page = 1;
    private $size = 20;
    private $settings = [];
    private $mappings = [];
    private $where = [];
    private $mwhere = "";
    private $source = [];
    private $sort = [];
    private $functions = [];
    private $search_after = [];
    private $has_child = [];
    private $has_parent = [];
    private $routing;
    private $isSql = false;

    public function __construct()
    {
        try {
            //构建客户端对象
            $hostType = env("ES_HOST_TYPE", "online");
            $host     = config("ESConfig." . $hostType, []);

            if (empty($host)){
                throw new Exception("Connect ES Server Failed,Not Config");
            }

            // 过滤未启用服务
            foreach ($host as $key => $value) {
                if (isset($value['isOpen']) && empty($value['isOpen'])) {
                    unset($host[$key]);
                }
            }

            $this->client = ClientBuilder::create()
                ->setHosts($host)
                ->setConnectionPool('\Elasticsearch\ConnectionPool\SimpleConnectionPool', [])
                ->setRetries(10)
                ->build();
        } catch (Exception $e) {
            throw $e;
        }
    }

    /**
     * 指定索引名称
     *
     * @param $iname string 索引名称 多索引以逗号分隔：eg:video,user
     *
     * @return $this
     */
    public function index($iname = '')
    {
        $params = ['index' => $iname];
        //if (strpos($iname,"*") == false && !$this->client->indices()->exists($params)){
        //    throw new Exception($iname."索引不存在");
        //}
        $this->index = $iname;
        return $this;
    }

    /**
     * 指定类型名称
     *
     * @param $tname
     *
     * @return $this
     */
    public function type($tname = '')
    {
        $this->type = $tname ?? '_doc';
        return $this;
    }

    /**
     * 返回sql
     *
     * @param $status
     *
     * @return $this
     */
    public function isSql($status)
    {
        $this->isSql = $status;
        return $this;
    }

    /**
     * 分页参数
     *
     * @param $page
     * @param $size
     *
     * @return $this
     * @author cc 2022/3/5 14:17
     */
    public function pageSize($page, $size)
    {
        $this->size = $size ?? 20;
        $this->page = $page ?? 1;
        return $this;
    }

    /**
     * 分片路由
     *
     * @param $routing
     *
     * @return $this
     */
    public function routing($routing)
    {
        $this->routing = $routing;
        return $this;
    }

    /**
     * settings参数
     *
     * @param int    $shards   数据分片数 按节点数配置 shards(1.5-3) * node(节点数)
     * @param int    $replicas 副本数
     * @param string $analyzer 指定默认分词
     */
    public function settings($params = [], $shards = 3, $replicas = 1, $analyzer = "ik_max_word")
    {
        $shards         = Arr::pull($params, "shards", $shards);
        $replicas       = Arr::pull($params, "replicas", $replicas);
        $analyzer       = Arr::pull($params, "analyzer", $analyzer);
        $this->settings = [
            'number_of_shards'   => $shards,
            'number_of_replicas' => $replicas,
            'index'              => ['analysis.analyzer.default.type' => $analyzer] // 默认ik中文分词器
        ];

        if (!empty($params)) {
            $this->settings = array_merge($this->settings, $params);
        }
        return $this;
    }

    /**
     * @param      $properties [字段名=>["type"=>"类型",...其余可附加参数]]
     * @param bool $_source
     *
     * @return $this
     */
    public function mappings($properties, $_source = true)
    {
        if (!empty($properties)) {
            $this->mappings = [
                '_source'    => [
                    'enabled' => $_source
                ],
                'properties' => $properties['properties'] ?? $properties
            ];
        }
        return $this;
    }

    /**
     * 超10000查询
     *
     * @param $status
     *
     * @return $this
     */
    public function track_total_hits($status)
    {
        $this->track_total_hits = $status;
        return $this;
    }

    /**
     * 限制返回字段  include:包含 (默认)
     *             exclude:排除
     *
     * fields=["include"=>["fieldName",...]] || ["exclude"=>["fieldName",...]]
     *
     * @param $fields
     *
     * @return $this
     * @author cc 2022/3/5 14:00
     */
    public function source($fields)
    {
        if (!empty($fields)) {
            $one = [];
            if (Arr::exists($fields, "include") || Arr::exists($fields,
                    "exclude")) {
                foreach ($fields as $k => $v) {
                    $one[$k] = $v;
                }
            } else {
                $one = $fields;
            }
            $this->source = $one;
        }
        return $this;
    }

    /**
     * 嵌入方法
     *
     * @param array $functions
     *
     * @return $this
     * @author cc 2022/3/7 10:05
     */
    public function functions($functions = [])
    {
        $this->functions = [];

        return $this;
    }

    /**
     * 查询数量
     *
     * @param int $size
     *
     * @return $this
     */
    public function size($size)
    {
        $this->size = $size;
        return $this;
    }

    /**
     * 排序
     *
     * @param $sort
     * sort=["field"=>"order","field"=>"order"]
     *
     * @return $this
     * @author cc 2022/3/5 14:32
     */
    public function sort($sort)
    {
        if (is_array($sort)) {
            $one = [];
            foreach ($sort as $value => $order) {
                if ($value == "_geo_distance"){
                    $one[] = [
                        "_geo_distance"=>$order
                    ];
                }else{
                    $one[] = [
                        $value => [
                            "order" => $order
                        ]
                    ];
                }

            }
            if (!empty($one)) {
                $this->sort = $one;
            }
        }
        return $this;
    }

    /**
     * 游标分页 下页
     *
     * @param $search_after
     *
     * @return $this
     */
    public function search_after($search_after)
    {
        $this->search_after = $search_after;
        return $this;
    }

    /**
     * 子查询
     *
     * @param $has_child
     *
     * @return $this
     */
    public function has_child($has_child)
    {
        $this->has_child = $has_child;
        return $this;
    }

    /**
     * 父查询
     *
     * @param $has_child
     *
     * @return $this
     */
    public function has_parent($has_parent)
    {
        $this->has_parent = $has_parent;
        return $this;
    }

    /**
     * 条件组装
     *
     * @param $where
     *
     * @return $this
     */
    public function where($where)
    {
        $res              = [];
        $track_total_hits = Arr::pull($where, "track_total_hits", "");
        foreach ($where as $key => $item) {
            $one = [];
            if (is_array($item) && $key != "bulk") {
                foreach ($item as $k => $v) {
                    $one = array_merge_recursive($one, $this->child($v, $k));
                }
            } else {
                $res = $item;
                break;
            }
            if (!empty($one)) {
                $res[$key] = $one;
            }
        }
        $res       = array_filter($res);
        $whereInfo = [];
        if (!empty($res) && count($res) > 1) {
            foreach ($res as $key => $item) {
                $whereInfo['must'][] = ["bool" => [$key => $item]];
            }
            $this->where = $whereInfo;
        } else {
            $this->where = $res;
        }
        if ($track_total_hits) {
            $this->where["track_total_hits"] = true;
        }
        return $this;
    }

    /**
     * 子条件组装
     *
     * @return array
     * @author cc 2022/3/5 10:48
     */
    private function child($data, $field)
    {
        if (empty($data) || is_array($data) == false) {
            throw new Exception("参数格式错误");
        }

        $res      = [];
        $childArr = [];
        switch ($field) {
            case "match":
            case "term":
            case "terms":
            case "wildcard":
            case "prefix":
            case "range":
            case "match_phrase":
            case "match_phrase_prefix":
            case "exists":
            case "text":
                //示例
                //default
                //data=[
                //    "field_name"=>"value",
                //    "field_name"=>"value",
                //    ...
                //]
                // range
                //data = [
                //    "field_name"=>[
                //        "gt|gte"=>"value",
                //        "lt|lte"=>"value",
                //    ],
                //    ...
                //]
                foreach ($data as $k => $v) {
                    if ($field == "term") {
                        $res[$field] = $v;
                    } elseif ($field == "exists") {
                        $res[$field] = ["field" => $v];
                    } else {
                        $res[$field] = [$k => $v];
                    }
                    $childArr[] = $res;
                }
                break;
            case "query_string":
                //data=[
                //    "field_name"=>["query"=>"value","boost"=>"20"],
                //    ...
                //]
                foreach ($data as $k => $v) {
                    $res[$field] = [
                        "default_field" => $k,
                        "query"         => $v['query'],
                        "boost"         => $v['boost'] ?? 1
                    ];
                    $childArr[]  = $res;
                }
                break;
            case "fuzzy":
                //data = [
                //    [
                //        "field_name"=>[
                //            "value"=>"值",
                //            "max_expansions|min_similarity"=>"值"
                //        ]
                //    ],
                //    [
                //        ......
                //    ]
                //]
                foreach ($data as $k => $v) {
                    $res[$field][$k] = [
                        "value" => $v['value'],
                    ];
                    if (isset($v['max_expansions'])) {
                        $res[$field][$k]['max_expansions'] = $v['max_expansions'];
                    }
                    if (isset($v['min_similarity'])) {
                        $res[$field][$k]['min_similarity'] = $v['min_similarity'];
                    }
                    $childArr[] = $res;
                }
                break;
            case "missing":
                //data=[
                //    "field_name",
                //    "field_name",
                //    ...
                //]
                foreach ($data as $item) {
                    $res['exists']          = ["field" => $item];
                    $childArr['must_not'][] = $res;
                }
                break;
            default:
                $childArr = [$field => $data];
                break;
        }
        return $childArr;
    }

    /**
     * 多次查询条件
     * mwhere = [
     *  [
     *      "index" => "video",
     *      "type"  => "_doc",
     *      "body"  => [
     *      "must" => ["range" => ["id" => ["gte" => 1, "lte" => "1000"]]],
     *  ]
     * ],
     * [
     *      "index" => "video1",
     *      "type"  => "_doc",
     *      "body"  => [
     *      "must" => ["range" => ["id" => ["gte" => 1000, "lte" => "3000"]]],
     *  ]
     * ]
     * ];
     *
     * @param $mWhere
     *
     * @return $this
     */
    public function mwhere($mWhere)
    {
        if (!is_array($mWhere)) {
            return $this;
        }
        foreach ($mWhere as $item) {
            if (!array_key_exists("index", $item) && empty($this->index)) {
                throw new Exception("多次查询未指定Index");
            }
            $query = json_encode([
                "index" => $item['index'] ?? $this->index,
                "type"  => $item['type'] ?? $this->type
            ], JSON_UNESCAPED_UNICODE);

            // 跳过无效body
            if (empty($item['body'])) {
                continue;
            }
            $body = [];
            // 字段过滤
            if (!empty($item['body']['source'])) {
                $source = Arr::pull($item['body'], "source");
                $this->source($source);
                if (!empty($this->source)) {
                    $body['_source'] = $this->source;
                }
            }
            // functions
            if (!empty($item['body']['functions'])) {
                $functions = Arr::pull($item['body'], "functions");
                $this->functions($functions);
                if (!empty($this->functions)) {
                    $body['functions'] = $this->functions;
                }
            }

            //排序
            if (!empty($item['body']['sort'])) {
                $sort = Arr::pull($item['body'], "sort");
                $this->sort($sort);
                if (!empty($this->sort)) {
                    $body['sort'] = $this->sort;
                }
            }

            // 游标分页
            if (!empty($item['body']['search_after'])) {
                $search_after = Arr::pull($item['body'], "search_after");
                $this->search_after($search_after);
                if (!empty($this->search_after)) {
                    $body['search_after'] = $this->search_after;
                }
            }

            // 普通分页
            if (!empty($item['body']['pageSize'])) {
                $pageSize     = Arr::pull($item['body'], "pageSize");
                $body['from'] = (($pageSize['page'] ?? 1) - 1) * ($pageSize['size'] ?? 20);
                $body['size'] = $pageSize['size'] ?? 20;
            }
            $this->where($item['body']);
            if (!empty($this->where)) {
                $body['query']['bool'] = $this->where;
            }
            $this->mwhere .= $query . "\n" . json_encode($body, JSON_UNESCAPED_UNICODE) . "\n";
        }
        return $this;
    }

    /**
     * 缓冲字段信息  array('字段名'=> 字段值,....)
     *
     * @param array $data
     *
     * @return $this
     */
    public function data($data = [])
    {
        $this->data = $data;
        return $this;
    }

    /**
     * 初始化索引参数
     *
     * @return array
     */
    public function initParams()
    {
        return [
            'index' => $this->index,
            'type'  => $this->type ?? "_doc",
        ];
    }

    /**
     * 插入/更新 使用ID-单条
     *
     * @param string $id
     *
     * @return bool
     * @throws Exception
     *
     */
    public function saveOneById(string $id)
    {
        try {
            $params = $this->initParams();
            if (empty($this->data)) {
                throw new Exception("None Added data");
            }
            $params['id']   = $id;
            $params['body'] = $this->data;
            if ($this->routing) {
                $params['routing'] = $this->routing;
            }
            $res = $this->client->index($params);
        } catch (Exception $e) {
            $info = [
                "index" => $this->index ?? "",
                "id"    => $id,
                "data"  => $this->data ?? [],
                "where" => $this->where ?? [],
                "msg"   => $e->getMessage()
            ];
            Log::info("saveOneById操作失败", $info);
            return false;
        }
        if (!empty($res['_shards']['successful'])) {
            return true;
        }
        return false;

    }

    /**
     * 修改
     *
     * @return bool
     * @throws Exception
     *
     */
    public function update()
    {
        try {
            $params = $this->initParams();

            if (empty($this->data)) {
                throw new Exception("Not Data");
            }
            if (empty($this->where)) {
                throw new Exception("Not Search Where");
            }
            $info = $this->size(10000)->track_total_hits(true)->source(["*"])->search();
            if (empty($info) || empty($info['data'])) {
                return 404;
            }
            foreach ($info['data'] as $k => $one) {
                $update = [
                    '_index' => $info["index"][$k],
                    '_type'  => $this->type,
                    "_id"    => $info["ids"][$k]
                ];
                if (!empty($this->routing)) {
                    $update['routing'] = $this->routing;
                }
                $params['body'][] = [
                    "update" => $update
                ];
                foreach ($this->data as $key => $value) {
                    if (isset($one[$key])) {
                        $one[$key] = $value;
                    }
                }
                $params['body'][] = ["doc" => $one];
            }
            $res = $this->client->bulk($params);
            if ($info['total'] > 10000) {
                return "-1";
            }
        } catch (Exception $e) {
            $info = [
                "index" => $this->index??"",
                "id"    => 0,
                "data"  => $this->data ?? [],
                "where"  => $this->where ?? [],
                "msg"   => $e->getMessage()
            ];
            Log::info("update操作失败", $info);
            return false;
        }
        if (!empty($res['error'])) {
            return false;
        }
        return true;


    }

    /**
     * 更新数据 以索引_id
     *
     * @return bool|string
     * @throws Exception
     */
    public function updateById($id = "")
    {
        try {
            $params = $this->initParams();
            if (empty($this->data) || empty($id)) {
                throw new Exception("缺少参数");
            }
            $params['body'] = ["doc" => $this->data];
            $params['id']   = $id;
            if ($this->isSql) {
                Log::info("ES:updateById执行语句:" . json_encode($params, JSON_UNESCAPED_UNICODE));
            }
            $res = $this->client->update($params);
            if ($this->isSql) {
                Log::info("ES:UpdateById执行结果集:" . json_encode($res, JSON_UNESCAPED_UNICODE));
            }
        } catch (Exception $e) {
            $info = [
                "index" => $this->index??"",
                "id"    => $id,
                "data"  => $this->data ?? [],
                "where"  => $this->where ?? [],
                "msg"   => $e->getMessage()
            ];
            Log::info("updateById操作失败", $info);
            return false;
        }
        if (!isset($res['_shards']['failed']) || $res['_shards']['failed']) {
            return false;
        }
        return true;

    }

    /**
     * 删除文档
     *
     * @param string $id 文档id
     *                   可选参数
     *                   where 当配置where条件时,查询删除符合条件的文档
     *                   id 当where未配置时,指定id删除文档
     *
     * @return bool
     * @throws Exception
     */
    public function delById($id = '')
    {
        if (!$this->index) {
            throw new Exception("索引不能为空！");
        }
        if ($this->where) {
            // 通过查询删除
            $params = [
                'index' => $this->index,
                'type'  => $this->type,
                'body'  => [
                    'query' => $this->where,
                ]
            ];
            if ($this->isSql) {
                Log::info("ES:DelById执行语句(包含where条件):" . json_encode($params, JSON_UNESCAPED_UNICODE));
            }
            $res = $this->client->deleteByQuery($params);
            if ($this->isSql) {
                Log::info("ES:DelById执行结果集(包含where条件):" . json_encode($res, JSON_UNESCAPED_UNICODE));
            }
            return true;
        }
        $res = $this->client->delete([
            'index' => $this->index,
            'type'  => $this->type,
            'id'    => $id
        ]);
        if ($this->isSql) {
            Log::info("ES:DelById执行结果集(以ID删除):" . json_encode($res, JSON_UNESCAPED_UNICODE));
        }
        if (isset($res['_shards']['failed']) && $res['_shards']['failed'] == 0) {
            return true;
        }
        return false;
    }


    /**
     * 根据where条件删除Es数据
     * @return bool
     * @throws Exception
     */
    public function delByWhere()
    {
        if (!$this->index) {
            throw new Exception("索引不能为空！");
        }
        if (empty($this->where)) {
            throw new Exception("Not Search Where");
        }
        try {
            // 通过查询删除
            $params = [
                'index' => $this->index,
                'type' => $this->type,
                'body' => [
                    'query' => ['bool' => $this->where],
                ]
            ];
            if ($this->isSql) {
                Log::info("ES:delByWhere执行语句(包含where条件):" . json_encode($params, JSON_UNESCAPED_UNICODE));
            }
            $res = $this->client->deleteByQuery($params);
            if ($this->isSql) {
                Log::info("ES:delByWhere执行结果集(包含where条件):" . json_encode($res, JSON_UNESCAPED_UNICODE));
            }
        } catch (Exception $e) {
            $info = [
                "index" => $this->index??"",
                "id"    => 0,
                "data"  => $this->data ?? [],
                "where"  => $this->where ?? [],
                "msg"   => $e->getMessage()
            ];
            Log::info("delByWhere操作失败", $info);
            return false;
        }
        if (!empty($res['error'])) {
            return false;
        }
        return true;
    }


    /**
     * 批量操作
     *
     * @param string $type 操作类型
     *                     默认index:没有创建,有则覆盖
     *                     create:没有创建,有则无效
     *                     update:修改
     *                     delete:删除
     *                     ### 批量操作只能同类型操作 暂不支持不同类型同时操作 ###
     * @param string $_id  指定_id对应字段
     *
     *                     data:[["xid"=>1,fieldName=>value],["xid"=>2,fieldName=>value]]
     *
     * @return array
     * @throws Exception
     *
     * @author cc 2022/3/5 16:47
     */
    public function bulkById($type = "index", $_id = "")
    {
        if (empty($this->index)) {
            throw new Exception("索引不能为空");
        }
        try {
            $params = [];
            if (empty($this->data)) {
                throw new Exception("Not Data");
            }
            foreach ($this->data as $item) {
                $body = [
                    $type => [
                        '_index' => $this->index,
                        '_type'  => $this->type
                    ]
                ];
                if (isset($item['routing'])) {
                    $body['index']['routing'] = $item["routing"];
                    unset($item['routing']);
                }

                if (!empty($item[$_id])) {
                    $body[$type]['_id'] = $item[$_id];
                } elseif (is_string($item) && $type == "delete") {
                    $body[$type]['_id'] = $item;
                } else {
                    continue;
                }
                switch ($type) {
                    case "delete":
                        $params['body'][] = $body;
                        break;
                    case "create":
                    case "index":
                    case "update":
                        $params['body'][] = $body;
                        if (empty($item)) {
                            $item = \stdClass::$item;
                        }
                        if ($type == "update") {
                            $params['body'][] = [
                                "doc" => $item
                            ];
                        } else {
                            $params['body'][] = $item;
                        }
                }
            }
            if ($this->isSql) {
                Log::info("ES:BulkById执行语句:" . json_encode($params, JSON_UNESCAPED_UNICODE));
            }
            $res = $this->client->bulk($params);
            if ($this->isSql) {
                Log::info("ES:BulkById批量操作结果集:" . json_encode($res, JSON_UNESCAPED_UNICODE));
            }
        } catch (Exception $e) {
            $info = [
                "index" => $this->index ?? "",
                "id"    => 0,
                "data"  => $this->data ?? [],
                "where" => $this->where ?? [],
                "msg"   => $e->getMessage()
            ];
            Log::info("bulkById操作失败", $info);
            return [
                "status"       => "error",
                "successCount" => 0,
                "errorCount"   => 0,
            ];
        }
        $lastData = [
            "status"       => "success",
            "successCount" => 0,
            "errorCount"   => 0,
        ];
        if (!empty($res['items'])) {
            foreach ($res['items'] as $key => $item) {
                if (!empty($item[$type]['_shards']['successful'])) {
                    $lastData[$key]['status'] = "success";
                    $lastData["successCount"] += 1;
                } else {
                    $lastData['status']       = "error";
                    $lastData[$key]['status'] = "error";
                    $lastData[$key]['reason'] = $item[$type]['error']['reason'];
                    $lastData["errorCount"]   += 1;
                }
            }
        }
        return $lastData;
    }

    /**
     * 批量添加
     *
     * @return array
     * @throws Exception
     *
     */
    public function bulkAdd()
    {
        if (empty($this->index)) {
            throw new Exception("索引不能为空");
        }
        try {
            $params = [];
            if (empty($this->data)) {
                throw new Exception("Not Data");
            }
            foreach ($this->data as $item) {
                $body = [
                    "index" => [
                        '_index' => $this->index,
                        '_type'  => $this->type
                    ]
                ];
                if (empty($item)) {
                    continue;
                }
                if (isset($item['routing'])) {
                    $body['index']['routing'] = $item["routing"];
                    unset($item['routing']);
                }
                $params['body'][] = $body;
                $params['body'][] = $item;
            }
            if ($this->isSql) {
                Log::info("ES:BulkAdd执行语句:" . json_encode($params, JSON_UNESCAPED_UNICODE));
            }
            $res = $this->client->bulk($params);
            if ($this->isSql) {
                Log::info("ES:BulkAdd批量添加结果集:" . json_encode($res, JSON_UNESCAPED_UNICODE));
            }
        } catch (Exception $e) {
            $info = [
                "index" => $this->index ?? "",
                "id"    => 0,
                "data"  => $this->data ?? [],
                "where" => $this->where ?? [],
                "msg"   => $e->getMessage()
            ];
            Log::info("bulkAdd操作失败", $info);
            return [
                "status"       => "error",
                "successCount" => 0,
                "errorCount"   => 0,
            ];
        }
        $lastData = [
            "status"       => "success",
            "successCount" => 0,
            "errorCount"   => 0,
        ];
        if (!empty($res['items'])) {
            foreach ($res['items'] as $key => $item) {
                if (!empty($item["index"]['_shards']['successful'])) {
                    $lastData[$key]['status'] = "success";
                    $lastData["successCount"] += 1;
                } else {
                    $lastData['status']       = "error";
                    $lastData[$key]['status'] = "error";
                    $lastData[$key]['reason'] = $item["index"]['error']['reason'];
                    $lastData["errorCount"]   += 1;
                }
            }
        }
        return $lastData;
    }


    /**
     * 原生ES语句查询
     * @param $arr
     * @return mixed
     * @throws Exception
     */
    public function NativeSearch($arr){
        try {
            $res = $this->client->search($arr);

        }catch (Exception $e){
            Log::info("NativeSearch操作失败".$e->getMessage());
            return [
                "total" => 0,
                "data"  => [],
                "ids"   => [],
                "index" => [],
                "inner" => [],
            ];
        }
        if (!isset($res['_shards']['successful'])) {
            return [];
        }
        return [
            "total" => $res['hits']['total']['value'],
            "data"  => array_column($res['hits']['hits'], "_source"),
            "ids"   => array_column($res['hits']['hits'], "_id"),
            "index" => array_column($res['hits']['hits'], "_index"),
            "inner" => array_column($res['hits']['hits'], "inner_hits"),
        ];
    }

    /**
     * 原生ES语句查询
     * @param $arr
     * @return mixed
     * @throws Exception
     */
    public function NativeSearchNew($arr){
        try {
            $res = $this->client->search($arr);

        }catch (Exception $e){
            Log::info("NativeSearchNew操作失败".$e->getMessage());
            return [
                "total" => 0,
                "data"  => []
            ];
        }
        if (!isset($res['_shards']['successful'])) {
            return [];
        }
        return [
            "total" => $res['hits']['total']['value'],
            "data"  => array_column($res['hits']['hits'], "_source"),
        ];
    }




    /**
     * 查询
     * 可选参数
     *      where       查询条件
     *      source      指定查询字段
     *      sort        排序
     *      pageSize    分页(page,size)
     *
     * @return array
     * @throws Exception
     *
     * @author cc 2022/3/3 14:38
     */
    public function search()
    {
        try {
            $params         = $this->initParams();
            $params['type'] = $this->type ?? "doc";
            $params['body'] = [];
            if (!empty($this->where)) {
                $params['body']["query"]['bool'] = $this->where;
            }
            //过滤字段
            if (!empty($this->source)) {
                $params['body']['_source'] = $this->source;
            }
            // 分页
            $params['body']['from'] = ($this->page - 1) * $this->size;
            $params['body']['size'] = $this->size;
            // 排序
            if (!empty($this->sort)) {
                $params['body']['sort'] = $this->sort;
            }

            // 游标分页
            // 下一页
            if (!empty($this->search_after)) {
                unset($params['body']['from']);
                $params['body']['search_after'] = $this->search_after;
            }
            // 父查询
            if (!empty($this->has_parent)) {
                $has_parent = $this->has_parent;
                if (!empty($params['body']["query"])) {
                    $has_parent['query'] = $params['body']["query"];
                    unset($params['body']["query"]);
                }
                $params["body"]["query"]['has_parent'] = $has_parent;
            }
            // 子查询
            if (!empty($this->has_child)) {
                $has_child          = $this->has_child;
                $has_child['query'] = $params['body']["query"] ?? [];
                unset($params['body']["query"]);
                $params["body"]["query"]['has_child'] = $has_child;
            }
            if (!empty($this->track_total_hits)) {
                $params["body"]['track_total_hits'] = true;
            }
            if ($this->isSql) {
                Log::info("ES:Search查询语句:" . json_encode($params, JSON_UNESCAPED_UNICODE));
            }
            $res = $this->client->search($params);
            if ($this->isSql) {
                Log::info("ES:Search查询结果集:" . json_encode($res, JSON_UNESCAPED_UNICODE));
            }
        } catch (Exception $e) {
            $info = [
                "index" => $this->index ?? "",
                "id"    => 0,
                "data"  => $this->data ?? [],
                "where" => $this->where ?? [],
                "msg"   => $e->getMessage()
            ];
            Log::info("search操作失败", $info);
            return [];
        }
        if (!isset($res['_shards']['successful'])) {
            return [];
        }
        $data = [
            "total" => $res['hits']['total']['value'],
            "data"  => array_column($res['hits']['hits'], "_source"),
            "ids"   => array_column($res['hits']['hits'], "_id"),
            "index" => array_column($res['hits']['hits'], "_index"),
        ];
        return $data;
    }

    /**
     * 原生es语句查询
     * @param array $where
     *
     * @return array
     */
    public function select($where)
    {
        $params         = $this->initParams();
        $params['type'] = $this->type ?? "doc";
        if (!empty($where['aggs'])){
            $params['body'] = $where;
        }else{
            $params['body']["query"] = $where;
        }
        //过滤字段
        if (!empty($this->source)) {
            $params['body']['_source'] = $this->source;
        }
        if (!empty($this->track_total_hits)) {
            $params["body"]['track_total_hits'] = true;
        }
        // 分页
        $params['body']['from'] = ($this->page - 1) * $this->size;
        $params['body']['size'] = $this->size;
        // 排序
        if (!empty($this->sort)) {
            $params['body']['sort'] = $this->sort;
        }
        if ($this->isSql) {
            Log::info("ES:Select查询语句:" . json_encode($params, JSON_UNESCAPED_UNICODE));
        }
        $res = $this->client->search($params);
        if ($this->isSql) {
            Log::info("ES:Select查询结果集:" . json_encode($res, JSON_UNESCAPED_UNICODE));
        }
        if (!isset($res['_shards']['successful'])) {
            return [];
        }

        $data = [
            "total" => $res['hits']['total']['value'],
            "data"  => array_column($res['hits']['hits'], "_source"),
            "ids"   => array_column($res['hits']['hits'], "_id"),
            "index" => array_column($res['hits']['hits'], "_index"),
            //"aggregations" => array_column($res['aggregations'], "buckets"),
        ];
        if (!empty($res['aggregations'])){
            $data['aggregations'] = array_column($res['aggregations'], "buckets");
        }
        return $data;

    }
    /**
     * 原生es语句查询
     * @param array $where
     *
     * @return array
     */
    public function EsSelect($body)
    {
        $params         = $this->initParams();
        $params['type'] = $this->type ?? "doc";
        $params['body'] = $body;
        // 分页
        if (empty($body['from'])){
            $params['body']['from'] = ($this->page - 1) * $this->size;
        }
        if (empty($body['size'])){
            $params['body']['size'] = $this->size;
        }
        if ($this->isSql) {
            Log::info("ES:Select查询语句:" . json_encode($params, JSON_UNESCAPED_UNICODE));
        }
        $res = $this->client->search($params);
        if ($this->isSql) {
            Log::info("ES:Select查询结果集:" . json_encode($res, JSON_UNESCAPED_UNICODE));
        }
        if (!isset($res['_shards']['successful'])) {
            Log::error("EsSearch方法查询失败,查询语句".json_encode($body));

        }
        return $res;
    }
    /**
     * 多查询
     *
     * @return array
     */
    public function msearch()
    {
        // 指定index
        if (!empty($this->index)) {
            $params = $this->initParams();
        }
        if (!empty($this->mwhere)) {
            $params['body'] = $this->mwhere;
            if ($this->isSql) {
                Log::info("ES:MSearch查询语句:" . var_export($params['body'],true));
            }
            $res = $this->client->msearch($params);
            if ($this->isSql) {
                Log::info("ES:MSearch查询结果集:" . json_encode($res, JSON_UNESCAPED_UNICODE));
            }
        }
        $list = [];
        if (empty($res['responses'])) {
            return $list;
        }
        foreach ($res["responses"] as $k => $item) {
            if (!isset($item['_shards']['successful'])) {
                $list[$k] = [];
                continue;
            }
            $list[$k] = [
                "total" => $item['hits']['total']['value'],
                "index" => array_column($item['hits']['hits'], "_index"),
                "_id"   => array_column($item['hits']['hits'], "_id"),
                "data"  => array_column($item['hits']['hits'], "_source")
            ];
        }

        return $list;
    }

    /**
     * 根据id查询数据
     * 返回[total= ,data=[]]
     *
     * @param $id string|array
     *
     * @return array
     * @throws Exception
     */
    public function searchById($id)
    {
        try {
            $params['index'] = $this->index;
            $arrType         = false;
            if (is_array($id)) {
                $arrType                                 = true;
                $params['body']["query"]["bool"]['must'] = ["terms" => ["_id" => $id]];
            } else {
                $params['body']["query"]["bool"]['must'] = ["term" => ["_id" => $id]];
            }
            if (!empty($this->source)) {
                $params['body']['_source'] = $this->source;
            }
            if ($this->isSql) {
                Log::info("ES:SearchById查询语句:" . json_encode($params, JSON_UNESCAPED_UNICODE));
            }
            $res = $this->client->search($params);
            if ($this->isSql) {
                Log::info("ES:SearchById查询结果集:" . json_encode($res, JSON_UNESCAPED_UNICODE));
            }
        } catch (Exception $e) {
            $info = [
                "index" => $this->index ?? "",
                "id"    => $id,
                "data"  => $this->data ?? [],
                "where" => $this->where ?? [],
                "msg"   => $e->getMessage()
            ];
            Log::info("searchById操作失败", $info);
            return [];
        }
        $r = [];
        if (!empty($res['_shards']['successful'])) {
            if ($arrType) {
                $r['total'] = $res['_shards']['total'];
                $r['data']  = array_column($res['hits']['hits'], "_source");
            } else {
                $r = $res['hits']['hits'][0]['_source'] ?? [];
            }
        }
        return $r;
    }

    /**
     * 批量查询，只能根据id来查
     *
     * @param $data
     *
     * @return array
     * @throws Exception
     */
    public function mGet($ids = [])
    {
        try {
            if (!is_array($ids)) {
                return [];
            }
            //初始化索引
            $params                = $this->initParams();
            $params['body']['ids'] = $ids;
            if ($this->isSql) {
                Log::info("ES:MGet查询语句:" . json_encode($params, JSON_UNESCAPED_UNICODE));
            }
            $list = $this->client->mget($params);
            if ($this->isSql) {
                Log::info("ES:MGet查询结果集:" . json_encode($list, JSON_UNESCAPED_UNICODE));
            }
            if (empty($list['docs'])) {
                return [];
            }
            $res = [];
            foreach ($list['docs'] as $item) {
                if (!empty($item['found'])) {
                    $res[] = $item['_source'];
                }
            }
            return $res;
        } catch (Exception $e) {
            $info = [
                "index" => $this->index ?? "",
                "id"    => 0,
                "data"  => $this->data ?? [],
                "where" => $this->where ?? [],
                "msg"   => $e->getMessage()
            ];
            Log::info("mGet操作失败", $info);
            return [];
        }
    }

    /**
     * 根据ID检测索引文档是否存在
     *
     * @param $id
     *
     * @return array|bool
     * @throws Exception
     */
    public function checkDocExistsById($id)
    {
        try {
            $params       = $this->initParams();
            $params['id'] = $id;
            $res          = $this->client->exists($params);
        } catch (Exception $e) {
            $info = [
                "index" => $this->index ?? "",
                "id"    => $id,
                "msg"   => $e->getMessage()
            ];
            Log::info("checkDocExistsById操作失败", $info);
            return false;
        }
        return $res;
    }

    /**
     * 创建索引
     *
     * settings 默认三分片、一副本、ik_max_word分词器
     * mapping  配置字段属性
     *
     * @return array
     * @throws Exception
     */
    public function createIndex($index = "")
    {
        try {
            if (is_null($index)) {
                throw new Exception("索引名称不能为空");
            }
            $initParams["index"] = strtolower($index);
            //setting 设置分片 副本 ik分词器
            if (empty($this->settings)) {
                $this->settings();
            }
            $initParams['body']['settings'] = $this->settings;
            // mapping 设置字段类型
            if (!empty($this->mappings)) {
                $initParams['body']['mappings']['doc'] = $this->mappings;
                //需配置include_type_name为true
                $initParams['custom']['include_type_name'] = true;
            }
            $res = $this->client->indices()->create($initParams);
            if ($this->isSql) {
                Log::info("ES:CreateIndex执行结果:" . json_encode($res, JSON_UNESCAPED_UNICODE));
            }
        } catch (Exception $e) {
            Log::info("createIndex操作失败".$e->getMessage());
            return [];
        }
        return $res;
    }


    /**
     * 修改副本数
     *
     * @param $replicas_num
     *
     * @return bool
     * @throws Exception
     */
    public function setReplicas($replicas_num)
    {
        if (empty($this->index)) {
            throw new Exception("请指定索引");
        }
        $params['index']                      = $this->index;
        $params['body']['number_of_replicas'] = $replicas_num ?? 1;
        $res                                  = $this->client->indices()->putSettings($params);
        if ($this->isSql) {
            Log::info("ES:SetReplicas执行结果:" . json_encode($res, JSON_UNESCAPED_UNICODE));
        }
        if (!empty($res['acknowledged'])) {
            return true;
        } else {
            return false;
        }
    }

    /**
     * 修改settings 配置
     *
     * @param array $settings
     *
     * @return array|false
     * @throws Exception
     */
    public function setSettings($settings = [])
    {
        try {
            if (empty($settings)) {
                return false;
            }
            //关闭索引
            $this->close();
            $initParams['index'] = $this->index;
            //设置settings信息
            $initParams['body'] = $settings;
            $res                = $this->client->indices()->putSettings($initParams);
            if ($this->isSql) {
                Log::info("ES:SetSettings执行结果:" . json_encode($res, JSON_UNESCAPED_UNICODE));
            }
            //开启索引
            $this->open();
        } catch (Exception $e) {
            Log::info("setSettings操作失败".$e->getMessage());
            return false;
        }
        return $res;
    }

    /**
     * 检查Index 是否存在
     *
     * @return bool
     */
    public function checkIndexExists()
    {
        try {
            $params = [
                'index' => $this->index
            ];

            return $this->client->indices()->exists($params);
        } catch (Exception $e) {
            Log::info("checkIndexExists操作失败".$e->getMessage());
            return false;
        }
    }

    /**
     * 删除索引
     *
     * @return false
     * @throws Exception
     */
    public function delIndex()
    {
        try {
            if ($this->checkIndexExists()) {
                $params['index'] = $this->index;
                $res             = $this->client->indices()->delete($params);
                if ($this->isSql) {
                    Log::info("ES:DelIndex执行结果:" . json_encode($res, JSON_UNESCAPED_UNICODE));
                }
                return $res['acknowledged'] ?? false;
            }
            return false;
        } catch (Exception $e) {
            $info = [
                "index" => $this->index ?? "",
                "id"    => 0,
                "msg"   => $e->getMessage()
            ];
            Log::info("delIndex操作失败", $info);
            return false;
        }
    }

    /**
     * 获取索引映射 mapping
     *
     * @return array
     * @throws Exception
     */
    public function getMapping()
    {
        try {
            $initParams['index'] = $this->index;
            return $this->client->indices()->getMapping($initParams);
        } catch (Exception $e) {
            Log::info("getMapping操作失败".$e->getMessage());
            return [];
        }
    }

    /**
     * 获取ES的状态信息，包括index列表
     *
     * @return array
     */
    public function ESStatus()
    {
        try {
            return $this->client->indices()->stats();
        } catch (Exception $e) {
            Log::info("ESStatus操作失败".$e->getMessage());
            return [];
        }
    }

    /**
     * 关闭索引
     *
     * @return bool
     * @throws Exception
     */
    public function close()
    {
        try {
            $initParams['index'] = $this->index;
            $res                 = $this->client->indices()->close($initParams);
            if ($this->isSql) {
                Log::info("ES:Close执行结果:" . json_encode($res, JSON_UNESCAPED_UNICODE));
            }
        } catch (Exception $e) {
            Log::info("close操作失败".$e->getMessage());
            return false;
        }
        if (!empty($res)) {
            return true;
        }
        return false;

    }

    /**
     * 开启索引
     *
     * @return bool
     * @throws Exception
     */
    public function open()
    {
        try {
            $initParams['index'] = $this->index;
            $res                 = $this->client->indices()->open($initParams);
            if ($this->isSql) {
                Log::info("ES:Open执行结果:" . json_encode($res, JSON_UNESCAPED_UNICODE));
            }
        } catch (Exception $e) {
            Log::info("open操作失败,index:{$this->index}".$e->getMessage());
            return false;
        }
        if (!empty($res)) {
            return true;
        }
        return false;
    }

    //使用自增
    public function saveOne($id=''){
        try {
            $params = $this->initParams();
            if (empty($this->data)) {
                throw new Exception("None Added data");
            }
            $params['body'] = $this->data;
            if ($this->routing) {
                $params['routing'] = $this->routing;
            }
            if(!empty($id)){
                $params['id'] = $id;
                $params['body'] = [];
                $params['body']['doc'] = $this->data;
                $res = $this->client->update($params);

            }else{
                $res = $this->client->index($params);
            }

        } catch (Exception $e) {
            $info = [
                "index" => $this->index ?? "",
                "id"    => $id,
                "data"  => $this->data ?? [],
                "where" => $this->where ?? [],
                "msg"   => $e->getMessage()
            ];
            Log::info("saveOne操作失败",$info);
            return false;
        }
        if (!empty($res['_shards']['successful'])) {
            return true;
        }
        return false;
    }

}
